﻿using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;

namespace TaskRecall.Data
{
    public enum TaskStatus { NotStarted, Active, Complete, Suspended, Canceled }

    [DataContract]
    public class NamedObject
    {
        private int id;
        private string name;
        [DataMember]
        internal Storage owner;

        [DataMember]
        public int ID {
            get {
                return id;
            }
            set {
                id = value;
                Changed();
            }
        }
        [DataMember]
        public string Name {
            get {
                return name;
            }
            set {
                name = value;
                Changed();
            }
        }

        protected void Changed() {
            if (owner != null) {
                owner.NotifyItemChange(this);
            }
        }
    }

    [DataContract]
    public class TreeObject<T> : NamedObject where T : TreeObject<T>
    {
        private List<T> subItems = new List<T>();
        private T parent;
        private List<T> SubItems {
            get {
                if (subItems == null) {
                    subItems = new List<T>();
                }
                return subItems;
            }
        }

        [DataMember]
        public T Parent {
            get {
                return parent;
            }
            set {
                if (parent != null && parent.IsChildOf((T)this) || parent == this) {
                    throw new ArgumentException("Circular reference");
                }
                if (parent != null) {
                    parent.SubItems.Remove((T)this);
                }
                parent = value;
                if (parent != null) {
                    parent.SubItems.Add((T)this);
                }
                Changed();
            }
        }

        public ReadOnlyCollection<T> Children { get { return SubItems.AsReadOnly(); } }

        public bool IsChildOf(T item) {
            T p = this.parent;
            while (p != null) {
                if (p == item) {
                    return true;
                }
                p = p.parent;
            }
            return false;
        }
    }

    [DataContract]
    public class Task : TreeObject<Task>
    {
        private DateTime startTime;
        private DateTime endTime;
        private string description;
        private int priority;
        private TaskStatus status;
        private string reminder;
        private int timeSpent;
        private string onDismiss;
        private int activeNo;

        [DataMember]
        private List<Category> categories = new List<Category>();

        [DataMember]
        public DateTime StartTime {
            get {
                return startTime;
            }
            set {
                startTime = value;
                Changed();
            }
        }
        [DataMember]
        public DateTime EndTime {
            get {
                return endTime;
            }
            set {
                endTime = value;
                Changed();
            }
        }
        [DataMember]
        public string Description {
            get {
                return description;
            }
            set {
                description = value;
                Changed();
            }
        }
        [DataMember]
        public int Priority {
            get {
                return priority;
            }
            set {
                priority = value;
                Changed();
            }
        }
        [DataMember]
        public TaskStatus Status {
            get {
                return status;
            }
            set {
                status = value;
                Changed();
            }
        }
        [DataMember]
        public string Reminder {
            get {
                return reminder;
            }
            set {
                reminder = value;
                Changed();
            }
        }
        [DataMember]
        public int TimeSpent {
            get {
                return timeSpent;
            }
            set {
                timeSpent = value;
                Changed();
            }
        }
        [DataMember]
        public string OnDismiss {
            get {
                return onDismiss;
            }
            set {
                onDismiss = value;
                Changed();
            }
        }

        public int ActiveNo {
            get {
                return activeNo;
            }
            set {
                activeNo = value;
                Changed();
            }
        }

        public ReadOnlyCollection<Category> Categories { get { return categories.AsReadOnly(); } }

        public Task() {
            status = TaskStatus.NotStarted;
        }
    }

    [DataContract]
    public class Category : TreeObject<Category>
    {
        private int order;

        [DataMember]
        public int Order { 
            get {
                return order;
            }
            set {
                order = value;
            }
        }

        public Category(string name) {
            Name = name;
        }
        public Category(string name, Category parent)
            : this(name) {
            Parent = parent;
        }
    }

    public delegate void NotifyEvent<T>(T item) where T : NamedObject;
    public abstract class ObjectsSelection<T> : IEnumerable<T> where T : TreeObject<T>
    {
        protected Storage storage;
        protected List<T> selected = new List<T>();

        public NotifyEvent<T> OnAdd;
        public NotifyEvent<T> OnRemove;
        public NotifyEvent<T> OnItemChange;

        public IComparer<T> Comparer { get; set; }

        public ObjectsSelection(Storage storage) {
            this.storage = storage;
            storage.OnAdd += NotifyAdd;
            storage.OnRemove += NotifyRemove;
            storage.OnItemChange += NotifyItemChange;
        }
        public void Add(T obj) {
            storage.Add(obj);
        }
        public void Remove(T obj) {
            storage.Remove(obj);
        }
        public virtual void Select(T obj) {
            if (!selected.Contains(obj)) {
                if (OnAdd != null) {
                    OnAdd(obj);
                }
                selected.Add(obj);
                if (Comparer != null) {
                    selected.Sort(Comparer);
                }
            }
        }
        public virtual void Release(T obj) {
            if (OnRemove != null) {
                OnRemove(obj);
            }
            selected.Remove(obj);
        }
        public void ReleaseAll() {
            while (selected.Count > 0) {
                Release(selected[0]);
            }
        }
        public IEnumerator<T> GetEnumerator() {
            return selected.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        protected virtual void NotifyAdd(NamedObject obj) {
            if (obj is T) {
                if (!selected.Contains((T)obj)) {
                    Select((T)obj);
                }
            }
        }
        protected virtual void NotifyRemove(NamedObject obj) {
            if (obj is T) {
                if (selected.Contains((T)obj)) {
                    Release((T)obj);
                }
            }
        }
        protected virtual void NotifyItemChange(NamedObject obj) {
            if (OnItemChange != null && obj is T && selected.Contains((T)obj)) {
                if (Comparer != null) {
                    selected.Sort(Comparer);
                }
                OnItemChange((T)obj);
            }
        }

    }

    public class SingletonSelection<T> : ObjectsSelection<T> where T : TreeObject<T>
    {
        public T Item {
            get {
                if (selected.Count > 0) {
                    return selected[0];
                } else {
                    return null;
                }
            }
            set {
                if (Item != null) {
                    Release(Item);
                }
                Select(value);
            }
        }
        public override void Select(T obj) {
            if (Item != null) {
                Release(Item);
            }
            base.Select(obj);
        }
        public SingletonSelection(Storage storage) : base(storage) { }
    }

    public delegate bool BelongsFilter<T>(T obj) where T : TreeObject<T>;
    public class FilterSelection<T> : ObjectsSelection<T> where T : TreeObject<T>
    {
        public event BelongsFilter<T> Filter;

        public FilterSelection(Storage storage) : base(storage) { }
        protected bool Belongs(T obj) {
            bool belongs = true;
            if (Filter != null) {
                foreach (BelongsFilter<T> filter in Filter.GetInvocationList()) {
                    if (!(bool)filter.DynamicInvoke(obj)) {
                        belongs = false;
                        break;
                    }
                }
            }
            return belongs;
        }

        public void UpdateSelection() {
            ReleaseAll();
            if (Comparer == null) {
                foreach (NamedObject obj in storage.Objects) {
                    if (obj is T && Belongs((T)obj)) {
                        Select((T)obj);
                    }
                }
            } else {
                List<T> l = new List<T>();
                foreach (NamedObject obj in storage.Objects) {
                    if (obj is T && Belongs((T)obj)) {
                        l.Add((T)obj);
                    }
                }
                l.Sort(Comparer);
                foreach (T obj in l) {
                    Select(obj);
                }
            }
        }

        protected override void NotifyAdd(NamedObject obj) {
            if (obj is T) {
                if (Belongs((T)obj)) {
                    base.NotifyAdd(obj);
                }
            }
        }
        protected override void NotifyItemChange(NamedObject obj) {
            if (obj is T) {
                bool belongs = Belongs((T)obj);
                if (selected.Contains((T)obj) && !belongs) {
                    Release((T)obj);
                } else if (!selected.Contains((T)obj) && belongs) {
                    Select((T)obj);
                } else {
                    base.NotifyItemChange(obj);
                }
            }
        }
    }

    [DataContract]
    public class Storage
    {
        public event NotifyEvent<NamedObject> OnAdd;
        public event NotifyEvent<NamedObject> OnRemove;
        public event NotifyEvent<NamedObject> OnItemChange;
        private string fileName;

        [DataMember]
        private int idGen;
        [DataMember]
        private Category allCategory = new Category("(all)");
        [DataMember]
        private Category noneCategory = new Category("(none)");
        [DataMember]
        private List<NamedObject> objects = new List<NamedObject>();

        public Category AllCategory { get { return allCategory; } }
        public Category NoneCategory { get { return noneCategory; } }
        public ReadOnlyCollection<NamedObject> Objects { get { return objects.AsReadOnly(); } }

        public static Storage Load(string fileName) {
            Storage data;
            try {
                FileStream fs = new FileStream(fileName, FileMode.Open);
                DataContractSerializer ser = new DataContractSerializer(typeof(Storage),
                              new Type[] { typeof(Category), typeof(Task), typeof(TaskStatus) },
                              Int32.MaxValue, true, true, null);
                data = (Storage)ser.ReadObject(fs);
                fs.Close();
            } catch (FileNotFoundException e) {
                e.ToString();
                data = new Storage();
                if (data.objects.Count == 0) {
                    data.Add(data.allCategory);
                    data.Add(data.noneCategory);
                }
            }
            data.fileName = fileName;
            return data;
        }
        public void Save() {
            FileStream fs = new FileStream(fileName, FileMode.Create);
            DataContractSerializer ser = new DataContractSerializer(typeof(Storage),
                          new Type[] { typeof(Category), typeof(Task), typeof(TaskStatus) },
                          Int32.MaxValue, true, true, null);
            ser.WriteObject(fs, this);
            fs.Close();
        }
        public void Add(NamedObject obj) {
            objects.Add(obj);
            obj.ID = idGen++;
            obj.owner = this;
            if (OnAdd != null) {
                OnAdd(obj);
            }
        }
        public void Remove(NamedObject obj) {
            objects.Remove(obj);
            obj.owner = null;
            if (OnRemove != null) {
                OnRemove(obj);
            }
        }
        public void Remove<T>(T obj) where T : TreeObject<T> {
            foreach (T subItem in obj.Children) {
                Remove(subItem);
            }
            objects.Remove(obj);
            obj.owner = null;
            if (OnRemove != null) {
                OnRemove(obj);
            }
        }
        internal void NotifyItemChange(NamedObject obj) {
            if (OnItemChange != null) {
                OnItemChange(obj);
            }
        }
        public bool IsSysCategory(object obj) {
            return obj == allCategory || obj == noneCategory;
        }
    }
}
